define([
  'common/geometry/geometry2d'],
function(
  geometry2d) {

function TerrainPhysicsWorld() {
  this.objects_ = [];

  /**
   * Polylines.
   */
  this.ledges_ = [];
}

TerrainPhysicsWorld.prototype.addTerrain = function(polyLine) {
  this.ledges_.push(polyLine);
};

TerrainPhysicsWorld.prototype.addObject = function(
  position, mass, frictionCoefficient, velocity) {
  // TODO: use position
  // TODO: use velocity
  var object = new TerrainObject(mass, frictionCoefficient, this.ledges_[0], 0);
  this.objects_.push(object);
  return object;
};

TerrainPhysicsWorld.prototype.update = function(timeDeltaMillis) {
  for (var i in this.objects_) {
    this.objects_[i].update_(timeDeltaMillis);
  }
};

TerrainObject = function(mass, frictionCoefficient, ledge, t) {
  this.mass_ = mass;
  this.frictionCoefficient_ = frictionCoefficient;
  this.ledge_ = ledge;
  this.t_ = t;

  this.forceAccumulator_ = geometry2d.zeroVector();
  this.parametricVelocity_ = 0;

  this.postUpdatePosition = this.ledge_.position(t);
};

/**
 * Applies a force in any direction. The tangential component accelerates
 * the object and the normal component generates friction.
 */
TerrainObject.prototype.applyForce = function(vector) {
  this.forceAccumulator_.add(vector);
};

/**
 * Applies a force along the terrain. The resulting force has the same magnitude
 * as the input.
 */
TerrainObject.prototype.applyForceAlongTerrain = function(vector) {
  var tangentComponent = this.ledge_.tangentComponent(this.t_, vector);
  if (tangentComponent.nonZero()) {
    debugger;
    this.applyForce(tangentComponent.normalize().scale(vector.length()));
  }
};

TerrainObject.prototype.update_ = function(timeDeltaMillis) {
  // TODO: after coming to rest, object moves right but not left.
  // if (this.forceAccumulator_.x != 0) {
  //   debugger;
  // }

  // Calculate friction and accumulated forces.
  var oldVelocity =
    this.ledge_.tangent(this.t_).scale(this.parametricVelocity_);
  var normalForceMagnitude =
    this.ledge_.normalComponentMagnitude(this.t_, this.forceAccumulator_);
  var tangentialForceMagnitude =
    this.ledge_.tangentComponentMagnitude(this.t_, this.forceAccumulator_);

  var accumulatedForcesAfterFriction;

  if (oldVelocity.nonZero()) {
    // Apply kinetic friction opposite direction of motion, which is always
    // parallel to surface.
    var frictionForce =
      oldVelocity.copy().normalize().flip().
      scale(normalForceMagnitude * this.frictionCoefficient_);
    accumulatedForcesAfterFriction =
      this.forceAccumulator_.copy().add(frictionForce);
  } else if (tangentialForceMagnitude >
             (normalForceMagnitude * this.frictionCoefficient_)) {
    // Apply static friction by comparing friction force to other forces and
    // ignoring other forces unless they are greater.
    // TODO: different coefficient
    var tangentialForce =
      this.ledge_.tangentComponent(this.t_, this.forceAccumulator_);
    frictionForce =
      tangentialForce.copy().normalize().flip().
      scale(normalForceMagnitude * this.frictionCoefficient_);
    accumulatedForcesAfterFriction =
      this.forceAccumulator_.copy().add(frictionForce);
  } else {
    // Object is at rest and other forces cannot overcome friction. Object
    // remains at rest.
    accumulatedForcesAfterFriction = geometry2d.zeroVector();
  }

  // Calculate new parametric velocity
  var parametricAcceleration =
    this.ledge_.parametricMagnitude(this.t_, accumulatedForcesAfterFriction) /
    this.mass_;
  var newParametricVelocity =
    this.parametricVelocity_ + (parametricAcceleration / timeDeltaMillis);

  // Update parametric position
  this.t_ += newParametricVelocity / timeDeltaMillis;

  // Calculate new Cartesian position
  this.postUpdatePosition = this.ledge_.position(this.t_);

  // Clear force accumulator.
  this.forceAccumulator_ = geometry2d.zeroVector();

  // Update parametric velocity.
  if ((this.parametricVelocity_ * newParametricVelocity) < 0) {
    // If parametric velocity has flipped, stop the object.
    this.parametricVelocity_ = 0;
  } else {
    this.parametricVelocity_ = newParametricVelocity;
  }

/*

  var frictionForce =
    velocity.normalize().copy().flip().multiply(normalForceMagnitude).
    scale(this.frictionCoefficient_);

  // If friction is greater than other forces, behave as if there are no
  // forces at all, otherwise add friction to the accumulator.
  if (frictionForce.length() <
      this.ledge_.tangentComponentMagnitude(this.t_, this.forceAccumulator_)) {
    // Friction is less than other forces. Add it to the accumulator.
    this.forceAccumulator_.add(frictionForce);
  } else {
    // Friction is greater than other forces.
    if (
    console.log('friction greater than other forces');
    debugger;
    this.forceAccumulator_ = geometry2d.zeroVector();
    this.parametricVelocity_ = 0;
  }

  // Update parametric velocity

  /*
  var tangentialForceMagnitude =
    this.ledge_.tangentComponentMagnitude(this.t_, this.forceAccumulator_);
  var tangentialAccelerationMagnitude = tangentialForceMagnitude / this.mass_;
  var parametricAcceleration =
    tangentialAccelerationMagnitude / this.ledge_.length();*

  var parametricAcceleration =
    this.ledge_.parametricMagnitude(this.t_, this.forceAccumulator_) /
    this.mass_;
  this.parametricVelocity_ += parametricAcceleration / timeDeltaMillis;

  // Update parametric position
  this.t_ += this.parametricVelocity_ / timeDeltaMillis;

  // Optional: if crossing to a new segment, recalculate forces...

  // Calculate new Cartesian position
  //   p = polyLine.getPosition(t)
  this.postUpdatePosition = this.ledge_.position(this.t_);

  // Clear force accumulator.
  this.forceAccumulator_ = geometry2d.zeroVector();
*/
};

return TerrainPhysicsWorld;

});
